// mainly a hack from portaudio examples

#include <stdio.h>
#include <stdlib.h>

#include "config.h"
#include "sound.h"


int recordCallback( const void *inputBuffer, void *outputBuffer,
                           unsigned long framesPerBuffer,
                           const PaStreamCallbackTimeInfo* timeInfo,
                           PaStreamCallbackFlags statusFlags,
                           void *userData )
{
    paTestData *data = (paTestData*)userData;
    const SAMPLE *rptr = (const SAMPLE*)inputBuffer;
    SAMPLE *wptr = &data->recordedSamples[data->frameIndex * NUM_CHANNELS];
    long framesToCalc;
    long i;
    int finished;
    unsigned long framesLeft = data->maxFrameIndex - data->frameIndex;

    (void) outputBuffer; /* Prevent unused variable warnings. */
    (void) timeInfo;
    (void) statusFlags;
    (void) userData;

    if( framesLeft < framesPerBuffer )
    {
        framesToCalc = framesLeft;
        finished = paComplete;
    }
    else
    {
        framesToCalc = framesPerBuffer;
        finished = paContinue;
    }

    if( inputBuffer == NULL )
    {
        for( i=0; i<framesToCalc; i++ )
        {
            *wptr++ = SAMPLE_SILENCE;  /* left */
            if( NUM_CHANNELS == 2 ) *wptr++ = SAMPLE_SILENCE;  /* right */
        }
    }
    else
    {
        for( i=0; i<framesToCalc; i++ )
        {
            *wptr++ = *rptr++;  /* left */
            if( NUM_CHANNELS == 2 ) *wptr++ = *rptr++;  /* right */
        }
    }
    data->frameIndex += framesToCalc;
    return finished;
}


int playCallback( const void *inputBuffer, void *outputBuffer,
                         unsigned long framesPerBuffer,
                         const PaStreamCallbackTimeInfo* timeInfo,
                         PaStreamCallbackFlags statusFlags,
                         void *userData )
{
    paTestData *data = (paTestData*)userData;
    SAMPLE *rptr = &data->recordedSamples[data->frameIndex * NUM_CHANNELS];
    SAMPLE *wptr = (SAMPLE*)outputBuffer;
    unsigned int i;
    int finished;
    unsigned int framesLeft = data->maxFrameIndex - data->frameIndex;

    (void) inputBuffer; /* Prevent unused variable warnings. */
    (void) timeInfo;
    (void) statusFlags;
    (void) userData;

    if( framesLeft < framesPerBuffer )
    {
        /* final buffer... */
        for( i=0; i<framesLeft; i++ )
        {
            *wptr++ = *rptr++;  /* left */
            if( NUM_CHANNELS == 2 ) *wptr++ = *rptr++;  /* right */
        }
        for( ; i<framesPerBuffer; i++ )
        {
            *wptr++ = 0;  /* left */
            if( NUM_CHANNELS == 2 ) *wptr++ = 0;  /* right */
        }
        data->frameIndex += framesLeft;
        finished = paComplete;
    }
    else
    {
        for( i=0; i<framesPerBuffer; i++ )
        {
            *wptr++ = *rptr++;  /* left */
            if( NUM_CHANNELS == 2 ) *wptr++ = *rptr++;  /* right */
        }
        data->frameIndex += framesPerBuffer;
        finished = paContinue;
    }
    return finished;
}


int record_raw(const char *file, int seconds)
{

  PaStreamParameters  inputParameters;
  PaStream*           stream;
  PaError             err = paNoError;
  paTestData          data;
  int                 i;
  int                 totalFrames;
  int                 numSamples;
  int                 numBytes;
  SAMPLE              max, val;
  double              average;
  
  //printf("patest_record.c\n");
  fflush(stdout);
  
  data.maxFrameIndex = totalFrames = seconds * SAMPLE_RATE; /* Record for a few seconds. */
  data.frameIndex = 0;
  numSamples = totalFrames * NUM_CHANNELS;
  numBytes = numSamples * sizeof(SAMPLE);
  data.recordedSamples = (SAMPLE *) malloc( numBytes ); /* From now on, recordedSamples is initialised. */
  if( data.recordedSamples == NULL )
    {
      printf("Could not allocate record array.\n");
      goto done;
    }
  for( i=0; i<numSamples; i++ ) data.recordedSamples[i] = 0;
  
  err = Pa_Initialize();
  if( err != paNoError ) goto done;
  
  inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */
  if (inputParameters.device == paNoDevice) {
    fprintf(stderr,"Error: No default input device.\n");
    goto done;
  }
  inputParameters.channelCount = NUM_CHANNELS;
  inputParameters.sampleFormat = PA_SAMPLE_TYPE;
  inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency;
  inputParameters.hostApiSpecificStreamInfo = NULL;
  
  /* Record some audio. -------------------------------------------- */
  err = Pa_OpenStream(
                      &stream,
                      &inputParameters,
                      NULL,                  /* &outputParameters, */
                      SAMPLE_RATE,
                      FRAMES_PER_BUFFER,
                      paClipOff,      /* we won't output out of range samples so don't bother clipping them */
                      recordCallback,
                      &data );
  if( err != paNoError ) goto done;
  
  err = Pa_StartStream( stream );
  if( err != paNoError ) goto done;
  printf("receiving"); fflush(stdout);
  
  while( ( err = Pa_IsStreamActive( stream ) ) == 1 )
    {
      Pa_Sleep(1000);
      printf("."); fflush(stdout);
    }
  printf("\n");
  if( err < 0 ) goto done;
  
  err = Pa_CloseStream( stream );
  if( err != paNoError ) goto done;
  
  /* Measure maximum peak amplitude. */
  max = 0;
  average = 0.0;
  for( i=0; i<numSamples; i++ )
    {
      val = data.recordedSamples[i];
      if( val < 0 ) val = -val; /* ABS */
      if( val > max )
        {
          max = val;
        }
      average += val;
    }
  
  average = average / (double)numSamples;
  
  //printf("sample max amplitude = "PRINTF_S_FORMAT"\n", max );
  //printf("sample average = %lf\n", average );
  
  /* Write recorded data to a file. */

  {
    FILE  *fid;
    fid = fopen(file, "wb");
    if( fid == NULL )
      {
        printf("Could not open file.");
      }
    else
      {
        fwrite( data.recordedSamples, NUM_CHANNELS * sizeof(SAMPLE), totalFrames, fid );
        fclose( fid );
        //printf("Wrote data to 'recorded.raw'\n");
      }
  }


  // handle errors 
 done:
  Pa_Terminate();
  if( data.recordedSamples )       /* Sure it is NULL or valid. */
    free( data.recordedSamples );
  if( err != paNoError )
    {
      fprintf( stderr, "An error occured while using the portaudio stream\n" );
      fprintf( stderr, "Error number: %d\n", err );
      fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );
      err = 1;          /* Always return 0 or 1, but no other return codes. */
    }
  return err;

}


int playback_raw(const char *file, int seconds)
{

  PaStreamParameters  outputParameters;
  PaStream*           stream;
  PaError             err = paNoError;
  paTestData          data;
  int                 i;
  int                 totalFrames;
  int                 numSamples;
  int                 numBytes;
  

  data.maxFrameIndex = totalFrames = seconds * SAMPLE_RATE;
  data.frameIndex = 0;
  numSamples = totalFrames * NUM_CHANNELS;
  numBytes = numSamples * sizeof(SAMPLE);
  data.recordedSamples = (SAMPLE *) malloc( numBytes ); /* From now on, recordedSamples is initialised. */
  if( data.recordedSamples == NULL )
    {
      printf("Could not allocate record array.\n");
      goto done;
    }

  {
    FILE  *fid;
    fid = fopen(file, "r");
    if( fid == NULL )
      {
        printf("Could not open file.");
      }
    else
      {
        fread( data.recordedSamples, NUM_CHANNELS * sizeof(SAMPLE), totalFrames, fid );
        fclose( fid );
        //printf("read 'recorded.raw'\n");
      }
  }

  //for( i=0; i<numSamples; i++ ) data.recordedSamples[i] = 0;

  
  err = Pa_Initialize();
  if( err != paNoError ) goto done;

  
  outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */
  //printf("Pa_GetDefaultOutputDevice:%d\n", Pa_GetDefaultOutputDevice());
  if (outputParameters.device == paNoDevice) {
    fprintf(stderr,"Error: No default output device.\n");
    goto done;
  }
  outputParameters.channelCount = NUM_CHANNELS;
  outputParameters.sampleFormat =  PA_SAMPLE_TYPE;
  outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency;
  outputParameters.hostApiSpecificStreamInfo = NULL;

  //printf("channels = %d\n", NUM_CHANNELS);
  
  //printf("\n=== Now playing back. ===\n"); fflush(stdout);
  err = Pa_OpenStream(
                      &stream,
                      NULL, /* no input */
                      &outputParameters,
                      SAMPLE_RATE,
                      FRAMES_PER_BUFFER,
                      paClipOff,      /* we won't output out of range samples so don't bother clipping them */
                      playCallback,
                      &data );
  if( err != paNoError ) goto done;
  
  if( stream )
    {
      err = Pa_StartStream( stream );
      if( err != paNoError ) goto done;
      
      //printf("Waiting for playback to finish.\n"); fflush(stdout);
      
      while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) Pa_Sleep(100);
      if( err < 0 ) goto done;
      
      err = Pa_CloseStream( stream );
      if( err != paNoError ) goto done;
      
      //printf("Done.\n"); fflush(stdout);
    }

  // handle errors 
 done:
  Pa_Terminate();
  if( data.recordedSamples )       /* Sure it is NULL or valid. */
    free( data.recordedSamples );
  if( err != paNoError )
    {
      fprintf( stderr, "An error occured while using the portaudio stream\n" );
      fprintf( stderr, "Error number: %d\n", err );
      fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );
      err = 1;          /* Always return 0 or 1, but no other return codes. */
    }
  return err;
  
}
